package za.org.jcicada.katcp;

/**
 * Parser.java
 *
 * @author Simon Cross
 *
 * Created on October 03, 2008, 13:45
 */

import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Parses lines into Message objects.
 */
public class Parser {

    // References to dictionaries used from Message

    /**
     * Map from message type characters to Type values.
     * 
     * @see Message#TYPE_SYMBOL_LOOKUP
     */
    protected static final Map<String, Message.Type> TYPE_SYMBOL_LOOKUP
        = Message.TYPE_SYMBOL_LOOKUP;

    /**
     * Map from escape codes to the values they represent.
     *
     * @see Message#ESCAPE_LOOKUP
     */
    protected static final Map<String, String> ESCAPE_LOOKUP
        = Message.ESCAPE_LOOKUP;

    /**
     * Map from characters that need escaping to their escape codes.
     *
     * @see Message#REVERSE_ESCAPE_LOOKUP
     */
    protected static final Map<String, String> REVERSE_ESCAPE_LOOKUP
        = Message.REVERSE_ESCAPE_LOOKUP;

    /**
     * Regular expression pattern matching argument delimiter.
     */
    protected static final Pattern ARGUMENT_SEP_RE = Pattern.compile("[ \\t]+");

    /**
     * Regular expression for finding un-escaped special characters.
     */
    protected static final Pattern SPECIAL_RE = Pattern.compile("[\\00\\n\\r\\x1b\\t ]");

    /**
     * Regular expression for un-escaping arguments.
     */
    protected static final Pattern UNESCAPE_RE = Pattern.compile("\\\\(.?)");

    /**
     * Parse escapes in a message argument.
     *
     * @param arg  The KATCP encoded argument.
     * @return  The decoded argument.
     */
    protected String parseArgument(String arg) {
        StringBuffer buffer = new StringBuffer();
        Matcher matcher;
        String escaped, unescaped;

        matcher = SPECIAL_RE.matcher(arg);
        if (matcher.find()) {
            throw new KatcpSyntaxException("Unescaped special '"
                                           + matcher.group() + "'");
        }

        matcher = UNESCAPE_RE.matcher(arg);
        while (matcher.find()) {
            escaped = matcher.group(1);
            if (escaped.length() == 0) {
                throw new KatcpSyntaxException("Escape slash at end of"
                                               + " argument.");
            }
            if (!ESCAPE_LOOKUP.containsKey(escaped)) {
                throw new KatcpSyntaxException("Unknown escape character '"
                                               + escaped + "'");
            }
            unescaped = ESCAPE_LOOKUP.get(escaped);
            // double escape the \ because matcher.appendReplacement
            // also unescapes it.
            unescaped = unescaped.replaceAll("\\\\","\\\\\\\\");
            matcher.appendReplacement(buffer, unescaped);
        }
        matcher.appendTail(buffer);

        return buffer.toString();
    }

    /**
     * Parse a line, return a Message.
     *
     * @param line  The String to parse.
     * @return  The Message created by parsing the line.
     */
    public Message parse(String line) {
        String [] parts;
        String typeChar;
        String name;
        Message.Type mtype;
        List<String> arguments = new LinkedList<String>();

        if (line.length() == 0)
            throw new KatcpSyntaxException("Empty message received.");

        // find command type and check validity

        typeChar = line.substring(0, 1);

        if (!TYPE_SYMBOL_LOOKUP.containsKey(typeChar))
            throw new KatcpSyntaxException("Bad type character '" + typeChar
                                           + "'.");

        mtype = TYPE_SYMBOL_LOOKUP.get(typeChar);

        // find command name and arguments

        parts = ARGUMENT_SEP_RE.split(line, 0);
        name = parts[0].substring(1);

        // parse arguments

        for (int i=1; i<parts.length; i++) {
            arguments.add(parseArgument(parts[i]));
        }

        return new Message(mtype, name, arguments.toArray(new String[0]));
    }

}
